#include "MapData.h"

#include "NumberUtility.h"
#include "StringUtility.h"

using namespace StringUtility;

MapData::MapData()
{
#ifdef _DEBUG
    pix.setColour(GREEN);
#endif
    m_isLoaded = false;
    propImages.loadImageSheet("images/debug/properties.png",3,12);
    propImages.setTransparentColour(Colour(WHITE));
}

MapData::~MapData()
{
    m_PropLayer.clear();
    m_FGLayer.clear();
    m_BGLayer.clear();
    m_MLayer.clear();
}

//----------------------------
// public functions
//----------------------------

void MapData::assign(const wstring &FGLayer, const wstring &BGLayer, const wstring &PropLayer, CRstring FilePath, CRint transColour, CRint width, CRint height)
{
    wstring MLayer;
    MLayer.assign(FGLayer.size(), 0xA1);
    assign(FGLayer, MLayer, BGLayer, PropLayer, FilePath, transColour, width, height);
}

void MapData::assign(const wstring &FGLayer, const wstring &MLayer, const wstring &BGLayer, const wstring &PropLayer, CRstring FilePath, CRint transColour, CRint width, CRint height)
{
    // TODO: loadImageSheet should take a FilePath and a TileSize, and calculate the sheet's width and height from the image width/tilesize, and height/tilesize.
    m_TileMap.loadImageSheetNoKey(FilePath,width,height);
    if (transColour >= 0)
        m_TileMap.setTransparentColour(Colour(transColour));
    else
        m_TileMap.disableTransparentColour();

    // Debug property images
    sizeOffset = (float)(propImages.getWidth() - m_TileMap.getWidth()) / 2.0;
    propImages.setScale((float)m_TileMap.getWidth() / (float)propImages.getWidth());

    // Initialize the tile m_CollisionMap
    m_FGLayer.resize(1);
    m_BGLayer.resize(1);
    m_MLayer.resize(1);
    m_PropLayer.resize(1);

    string prop;
    stringstream sstr;

    for(uint i=0; i < FGLayer.size(); i++)
    {
        // If the current character is and end of line character and it is not the
        // last character in the string,
        if (FGLayer[i] == '\n')
        {
            if (i != FGLayer.size()-1)
            {
                m_FGLayer.resize(m_FGLayer.size()+1);
                m_BGLayer.resize(m_BGLayer.size()+1);
                m_MLayer.resize(m_MLayer.size()+1);
                m_PropLayer.resize(m_PropLayer.size()+1); // Resize the m_PropLayer to hold one more row of tiles
            }
        }
        else
        {
            m_FGLayer[m_FGLayer.size()-1].resize(m_FGLayer[m_FGLayer.size()-1].size()+1);
            m_BGLayer[m_BGLayer.size()-1].resize(m_BGLayer[m_BGLayer.size()-1].size()+1);
            m_MLayer[m_MLayer.size()-1].resize(m_MLayer[m_MLayer.size()-1].size()+1);
            m_PropLayer[m_PropLayer.size()-1].resize(m_PropLayer[m_PropLayer.size()-1].size()+1);
            m_FGLayer[m_FGLayer.size()-1][m_FGLayer[m_FGLayer.size()-1].size()-1] = int(FGLayer[i])-161;
            m_BGLayer[m_BGLayer.size()-1][m_BGLayer[m_BGLayer.size()-1].size()-1] = int(BGLayer[i])-161;
            m_MLayer[m_MLayer.size()-1][m_MLayer[m_MLayer.size()-1].size()-1] = int(MLayer[i])-161;
            prop = baseConvert(int(PropLayer[i])-161);
            m_PropLayer[m_PropLayer.size()-1][m_PropLayer[m_PropLayer.size()-1].size()-1] = prop;
        }
    }

#ifdef _DEBUG
    cout << "--- Collision Map ---" << endl;
    for (uint I = 0; I < m_PropLayer.size(); I++)
    {
        for (uint K = 0; K < m_PropLayer[I].size(); K++)
        {
            cout << m_PropLayer[I][K] << " ";
        }
        cout << endl;
    }
    cout << "--- Foreground layer ---" << endl;
    for (uint I = 0; I < m_FGLayer.size(); I++)
    {
        for (uint K = 0; K < m_FGLayer[I].size(); K++)
        {
            cout << m_FGLayer[I][K] << " ";
        }
        cout << endl;
    }
    cout << "--- Background layer ---" << endl;
    for (uint I = 0; I < m_BGLayer.size(); I++)
    {
        for (uint K = 0; K < m_BGLayer[I].size(); K++)
        {
            cout << m_BGLayer[I][K] << " ";
        }
        cout << endl;
    }
    cout << "--- Middle layer ---" << endl;
    for (uint I = 0; I < m_MLayer.size(); I++)
    {
        for (uint K = 0; K < m_MLayer[I].size(); K++)
        {
            cout << m_MLayer[I][K] << " ";
        }
        cout << endl;
    }
#endif

    m_isLoaded = true;
}

int MapData::getCollisionData(CRuint x, CRuint y) const
{
    return getPropertyData(x,y,COLLISION);
}

int MapData::getPropertyData(CRuint x, CRuint y, const PROPERTIES prop) const
{
    if (y >= m_PropLayer.size() || x >= m_PropLayer[y].size())
    {
        return -1;
    }
    else
    {
        string tile = m_PropLayer[y][x];
        tile.insert(0,6-tile.length(),'0');
        return charToInt(tile.at(tile.length()-prop-1));
    }
}

int MapData::getTileData(CRuint x, CRuint y, const LAYERS layer) const
{
    if (y >= m_BGLayer.size() || x >= m_BGLayer[y].size())
    {
        return -1;
    }
    else
    {
        if (layer == BG)
        {
            return m_BGLayer[y][x];
        }
        else if (layer == MID)
        {
            return m_MLayer[y][x];
        }
        else
        {
            return m_FGLayer[y][x];
        }
    }
}

void MapData::setTileData(CRuint x, CRuint y, const LAYERS layer, CRint value)
{
    if (y < m_BGLayer.size() && x < m_BGLayer[y].size())
    {
        if (layer == BG)
        {
            m_BGLayer[y][x] = value;
        }
        else if (layer == MID)
        {
            m_MLayer[y][x] = value;
        }
        else
        {
            m_FGLayer[y][x] = value;
        }
    }
}

void MapData::slideTileData(CRuint x, CRuint y, CRuint width, CRuint height, CRint xoffset, CRint yoffset)
{
    if (xoffset != 0)
    {
        slideTileDataX(x,y,width,height,xoffset);
    }
    if (yoffset != 0)
    {
        slideTileDataY(x,y,width,height,yoffset);
    }
}

int MapData::getTileSize() const
{
    if (m_isLoaded) return m_TileMap.getWidth();
    else return 48;
}

Vector2df MapData::getSize() const
{
    return Vector2df(m_FGLayer[0].size(),m_FGLayer.size());
}

void MapData::setPropertyData(CRuint x, CRuint y, const PROPERTIES prop, CRint value)
{
    if (y < m_BGLayer.size() && x < m_BGLayer[y].size())
    {
        string tile = m_PropLayer[y][x];
        tile.insert(0,6-tile.length(),'0');
        tile.replace(tile.length()-prop-1,1,intToString(value));
        m_PropLayer[y][x] = tile;
    }
}

void MapData::render(const LAYERS layer, const Vector2df offset)
{
    uint size = m_TileMap.getWidth();
    uint sw = GFX::getXResolution();
    uint sh = GFX::getYResolution();

    int minY = max((int)floor(offset.y / size) - 1,0);
    int maxY = min((int)ceil((offset.y + sh) / size) + 1,(int)m_PropLayer.size());
    int minX = max((int)floor(offset.x / size) - 1,0);
    int maxX = min((int)ceil((offset.x + sw) / size) + 1,(int)m_PropLayer[0].size());

    int startX = ((int)offset.x % size) * -1;
    if (offset.x >= size)
        startX -= size;
    int x = startX;
    int y = ((int)offset.y % size) * -1;
    if (offset.y >= size)
        y -= size;

    if (layer != PROP)
    {
        vector< vector<int> > *tiledata;
        if (layer == BG)
        {
            tiledata = &m_BGLayer;
        }
        else if (layer == MID)
        {
            tiledata = &m_MLayer;
        }
        else
        {
            tiledata = &m_FGLayer;
        }

        for (int I = minY; I < maxY; ++I)
        {
            for (int K = minX; K < maxX; ++K)
            {
                //getTileData does extra range checking that we've already done here
                //and does a layer test every call, when we can cache the results of
                //that test.
                int tileId = (*tiledata)[I][K] - 1;
                if (tileId >= 0)
                {
                    m_TileMap.renderImage(tileId, x, y);
                }
                x += size;
            }
            x = startX;
            y += size;
        }
    }
    else
    {
        for (int I = minY; I < maxY; ++I)
        {
            for (int K = minX; K < maxX; ++K)
            {
                string tile = m_PropLayer[I][K];
                tile.insert(0,6-tile.length(),'0');
                for (int L = 0; L < 5; ++L)
                {
                    int prop = charToInt(tile.at(tile.length()-L-1));
                    if (prop > 0)
                        propImages.renderImage((L * 7 + prop - 1), x-sizeOffset, y-sizeOffset);
                }
                x += size;
            }
            x = startX;
            y += size;
        }
    }
}

//----------------------------
// utility functions
//----------------------------

void MapData::removeCameraTiles(const CollisionObject* const obj, const Vector2df& pos, const Vector2df& tilesX, const Vector2df& tilesY)
{
    for (int I = 0; I < obj->getSizeX(); ++I) // vertical
    {
        // correction must be zero so unaccessible tiles are not affected
        if ((getPropertyData(obj->getTileX(I),GAME) == 1) && obj->getCorrectionX(I) == Vector2df(0,0))
        {
            // found something, remove the whole visible row
            int x = obj->getTileX(I).x;
            int minY = tilesY.x;
            int maxY = tilesY.y;
            for (int K = minY; K <= maxY; ++K)
            {
                setPropertyData(x,K,GAME,0);
            }
            break;
        }
    }
    for (int I = 0; I < obj->getSizeY(); ++I) // horizontal
    {
        // correction must be zero so unaccessible tiles are not affected
        if ((getPropertyData(obj->getTileY(I),GAME) == 1) && obj->getCorrectionY(I) == Vector2df(0,0))
        {
            // found something, remove the whole visible row
            int y = obj->getTileY(I).y;
            int minX = tilesX.x;
            int maxX = tilesX.y;
            for (int K = minX; K <= maxX; ++K)
            {
                setPropertyData(K,y,GAME,0);
            }
            break;
        }
    }
}

Vector2df MapData::breakTiles(const CollisionObject* const obj, CRint state, CRbool dirX, CRbool dirY)
{
    Vector2df result = Vector2df(0,0);
    if (dirX)
    {
        for (int I = 0; I < obj->getSizeX(); ++I)
        {
            if (getPropertyData(obj->getTileX(I),BREAKABLE) == state)
            {
                setPropertyData(obj->getTileX(I),COLLISION,0);
                setPropertyData(obj->getTileX(I),BREAKABLE,0);
                setTileData(obj->getTileX(I),FG,-1);
            }
            else
            {
                if (getCollisionData(obj->getTileX(I)) != 0)
                {
                    CRfloat val = obj->getCorrectionX(I).x;
                    if (abs(val) > abs(result.x))
                        result.x = val;
                }
            }
        }
    }
    if (dirY)
    {
        for (int I = 0; I < obj->getSizeY(); I++)
        {
            if (getPropertyData(obj->getTileY(I),BREAKABLE) == state)
            {
                setPropertyData(obj->getTileY(I),COLLISION,0);
                setPropertyData(obj->getTileY(I),BREAKABLE,0);
                setTileData(obj->getTileY(I),FG,-1);
            }
            else
            {
                if (getCollisionData(obj->getTileY(I)) != 0)
                {
                    CRfloat val = obj->getCorrectionY(I).y;
                    if (abs(val) > abs(result.y))
                        result.y = val;
                }
            }
        }
    }
    return result;
}

//----------------------------
// private functions
//----------------------------

string MapData::baseConvert(CRint number) const
{
    if (number < 0)
    {
#if _DEBUG
        cout << "Prop error: " << number << ": ";
#endif
        return "Error";
    }
    if (number == 0)
    {
        return "0";
    }
    string output = "";
    int worker = number;
    while (worker > 0)
    {
        output = intToString(worker%8) + output;
        worker /= 8;
    }
    return output;
}

void MapData::slideTileDataX(CRuint x, CRuint y, CRuint width, CRuint height, CRint xoffset)
{
    uint i,j;
    if (xoffset == 0) return;
    //int FGWrap[height][xoffset > 0?xoffset:0-xoffset];
    //int BGWrap[height][xoffset > 0?xoffset:0-xoffset];
    //PROPERTIES PropWrap[height][xoffset > 0?xoffset:0-xoffset];
    vector<string> PropWrap;
    vector<int> FGWrap;
    vector<int> BGWrap;

    PropWrap.resize(height);
    FGWrap.resize(height);
    BGWrap.resize(height);

    if (xoffset > 0)
    {
        PropWrap.resize(xoffset);
        FGWrap.resize(xoffset);
        BGWrap.resize(xoffset);
        for (j=y; j<y+height; j++)
        {
            /* move to the right, so copy the cells to the right */
            std::copy(m_PropLayer[j].begin()+x+width-xoffset,m_PropLayer[j].begin()+x+width,PropWrap.begin());
            std::copy(m_FGLayer[j].begin()+x+width-xoffset,m_FGLayer[j].begin()+x+width,FGWrap.begin());
            std::copy(m_BGLayer[j].begin()+x+width-xoffset,m_BGLayer[j].begin()+x+width,BGWrap.begin());

            /* Now work backwards, moving each item xoffset positions to the right*/
            for (i = x+width; i>x+xoffset; i--)
            {
                m_PropLayer[j][i] = m_PropLayer[j][i-xoffset];
                m_FGLayer[j][i] = m_FGLayer[j][i-xoffset];
                m_BGLayer[j][i] = m_BGLayer[j][i-xoffset];
            }

            /* Now work forward again, copying from the backups we took */
            for (i = 0; i<(uint)xoffset; i++)
            {
                m_PropLayer[j][i+x] = PropWrap[i];
                m_FGLayer[j][i+x] = FGWrap[i];
                m_BGLayer[j][i+x] = BGWrap[i];
            }
        }
    }
    else
    {
        PropWrap.resize(0-xoffset);
        FGWrap.resize(0-xoffset);
        BGWrap.resize(0-xoffset);

        for (j=y; j<y+height; j++)
        {
            /* move to the left, so copy the cells to the left */
            std::copy(m_PropLayer[j].begin()+x,m_PropLayer[j].begin()+x-xoffset,PropWrap.begin());
            std::copy(m_FGLayer[j].begin()+x,m_FGLayer[j].begin()+x-xoffset,FGWrap.begin());
            std::copy(m_BGLayer[j].begin()+x,m_BGLayer[j].begin()+x-xoffset,BGWrap.begin());

            /* Now work forwards, moving each item xoffset positions to the left*/
            for (i = x; i>x+width-xoffset; i++)
            {
                m_PropLayer[j][i] = m_PropLayer[j][i-xoffset];
                m_FGLayer[j][i] = m_FGLayer[j][i-xoffset];
                m_BGLayer[j][i] = m_BGLayer[j][i-xoffset];
            }

            /* Now work forward again, copying from the backups we took */
            for (i = 0; i<(uint)(0-xoffset); i++)
            {
                m_PropLayer[j][i+width+x] = PropWrap[i];
                m_FGLayer[j][i+width+x] = FGWrap[i];
                m_BGLayer[j][i+width+x] = BGWrap[i];
            }
        }
    }
}

//TODO: Implement slideTileDataY
void MapData::slideTileDataY(CRuint x, CRuint y, CRuint width, CRuint height, CRint yoffset)
{
    if (yoffset == 0) return;
    cout << "This is not implemented yet!" << endl;
    exit(-1);
}

